From 0276d22c9ed49b035129cac99f68ada7fc1015d2 Mon Sep 17 00:00:00 2001
From: Kit La Touche <kit@transneptune.net>
Date: Fri, 29 Nov 2024 14:38:57 -0500
Subject: Set last-read on a channel on some events

Esc key, 2 second wait after scroll, and whenever the messages inChannel
change. Kinda gross set of things, but, so it goes. This does offer us
the option of extending this to include "when you click 'unread from
here' on a message" in future.
---
 ui/routes/(app)/ch/[channel]/+page.svelte | 60 +++++++++++++++++++++++++++++--
 1 file changed, 58 insertions(+), 2 deletions(-)

(limited to 'ui/routes/(app)/ch/[channel]/+page.svelte')

diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte
index 0961665..7d5b8cf 100644
--- a/ui/routes/(app)/ch/[channel]/+page.svelte
+++ b/ui/routes/(app)/ch/[channel]/+page.svelte
@@ -1,14 +1,70 @@
 <script>
+  import { DateTime } from 'luxon';
   import { page } from '$app/stores';
   import ActiveChannel from '$lib/components/ActiveChannel.svelte';
   import MessageInput from '$lib/components/MessageInput.svelte';
-  import { messages } from '$lib/store';
+  import { channelsList, messages } from '$lib/store';
 
   let channel = $derived($page.params.channel);
   let messageRuns = $derived($messages.inChannel(channel));
+  let activeChannel;
+
+  function inView(parentElement, element) {
+    const parRect = parentElement.getBoundingClientRect();
+    const parentTop = parRect.top;
+    const parentBottom = parRect.bottom;
+
+    const elRect = element.getBoundingClientRect();
+    const elementTop = elRect.top;
+    const elementBottom = elRect.bottom;
+
+    return ((parentTop < elementTop) && (parentBottom > elementBottom));
+  }
+
+  function getLastVisibleMessage() {
+    const parentElement = activeChannel;
+    const childElements = parentElement.getElementsByClassName('message');
+    const lastInView = Array.from(childElements).reverse().find((el) => {
+      return inView(parentElement, el);
+    });
+    return lastInView;
+  }
+
+  function setLastRead() {
+    const channelObject = $channelsList.getChannel(channel);
+    const lastInView = getLastVisibleMessage();
+    if (!channelObject || !lastInView) {
+      return;
+    }
+    const at = DateTime.fromISO(lastInView.dataset.at);
+    // Do it this way, rather than with Math.max tricks, to avoid assignment
+    // when we don't need it, to minimize reactive changes:
+    if (at > channelObject.lastReadAt) {
+      channelObject.lastReadAt = at;
+    }
+  }
+
+  $effect(() => {
+    const _ = $messages.inChannel(channel);
+    setLastRead();
+  });
+
+  function handleKeydown(event) {
+		if (event.key === 'Escape') {
+      setLastRead(); // TODO: pass in "last message DT"?
+    }
+  }
+
+  let lastReadCallback = null;
+  function handleScroll() {
+    clearTimeout(lastReadCallback);  // Fine if lastReadCallback is null still.
+    lastReadCallback = setTimeout(setLastRead, 2 * 1000);
+  }
 </script>
 
-<div class="active-channel">
+<svelte:window onkeydown={handleKeydown} />
+
+<div class="active-channel" on:scroll={handleScroll} bind:this={activeChannel}>
   <ActiveChannel {messageRuns} />
 </div>
 <div class="create-message max-h-full">
-- 
cgit v1.2.3

